﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Microsoft.Practices.CompositeUI.UIElements;
using Kent.Boogaart.HelperTrinity;

namespace WPFCABShell.$safeprojectname$
{
	/// <summary>
	/// A <see cref="UIElementAdapter{T}"/> that adds support for grouping and sorting of items within an <see cref="ItemsControl"/>.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class allows <see cref="ItemsControl"/>s to be adapted. Items in the <see cref="ItemsControl"/> can be grouped and sorted by applying
	/// a group index and index accordingly. All items within the same group will appear together in order of their index. Groups are separated
	/// by instances of <see cref="Separator"/>.
	/// </para>
	/// <para>
	/// Note that this adapter explicitly sets the <c>ItemsSource</c> for the <c>ItemsControl</c> and relies on it being left as such. If other
	/// code changes either the <c>ItemsSource</c> or explicitly adds items to the <c>Items</c> collection, the adapter will have no effect.
	/// </para>
	/// </remarks>
	public class GroupingItemsControlUIAdapter : UIElementAdapter<DependencyObject>
	{
		private readonly ItemsControl _itemsControl;
		private readonly GroupedAndSortedItems _groupedAndSortItems;

		/// <summary>
		/// Identifies the <c>GroupIndex</c> attached property.
		/// </summary>
		public static readonly DependencyProperty GroupIndexProperty = DependencyProperty.RegisterAttached("GroupIndex",
			typeof(int?),
			typeof(GroupingItemsControlUIAdapter),
			new FrameworkPropertyMetadata(GroupIndex_Changed));

		/// <summary>
		/// Identifies the <c>Index</c> attached property.
		/// </summary>
		public static readonly DependencyProperty IndexProperty = DependencyProperty.RegisterAttached("Index",
			typeof(int?),
			typeof(GroupingItemsControlUIAdapter),
			new FrameworkPropertyMetadata(Index_Changed));

		private static readonly DependencyProperty OwnerProperty = DependencyProperty.RegisterAttached("Owner",
			typeof(GroupedAndSortedItems),
			typeof(GroupingItemsControlUIAdapter));

		/// <summary>
		/// Constructs an instance of <c>GroupingItemsControlUIAdapter</c> for the specified <see cref="ItemsControl"/>.
		/// </summary>
		/// <param name="itemsControl">
		/// The <see cref="ItemsControl"/> to be adapted.
		/// </param>
		public GroupingItemsControlUIAdapter(ItemsControl itemsControl)
		{
			ArgumentHelper.AssertNotNull(itemsControl, "itemsControl");
			_itemsControl = itemsControl;
			_groupedAndSortItems = new GroupedAndSortedItems(_itemsControl);

			Binding binding = new Binding();
			binding.Source = _groupedAndSortItems;
			BindingOperations.SetBinding(_itemsControl, ItemsControl.ItemsSourceProperty, binding);
		}

		protected override DependencyObject Add(DependencyObject item)
		{
			SetOwner(item, _groupedAndSortItems);
			_groupedAndSortItems.Add(item);
			return item;
		}

		protected override void Remove(DependencyObject item)
		{
			_groupedAndSortItems.Remove(item);
			SetOwner(item, null);
		}

		/// <summary>
		/// Gets the group index for the specified item.
		/// </summary>
		/// <param name="item">
		/// The item.
		/// </param>
		/// <returns>
		/// The item's group index, or <see langword="null"/> if it has none.
		/// </returns>
		public static int? GetGroupIndex(DependencyObject item)
		{
			ArgumentHelper.AssertNotNull(item, "item");
			return (int?)item.GetValue(GroupIndexProperty);
		}

		/// <summary>
		/// Sets an item's group index.
		/// </summary>
		/// <param name="item">
		/// The item.
		/// </param>
		/// <param name="groupIndex">
		/// The group index to apply to the item.
		/// </param>
		public static void SetGroupIndex(DependencyObject item, int? groupIndex)
		{
			ArgumentHelper.AssertNotNull(item, "item");
			item.SetValue(GroupIndexProperty, groupIndex);
		}

		/// <summary>
		/// Gets the index for the specified item.
		/// </summary>
		/// <param name="item">
		/// The item.
		/// </param>
		/// <returns>
		/// The item's index, or <see langword="null"/> if it has none.
		/// </returns>
		public static int? GetIndex(DependencyObject item)
		{
			ArgumentHelper.AssertNotNull(item, "item");
			return (int?)item.GetValue(IndexProperty);
		}

		/// <summary>
		/// Sets an item's index.
		/// </summary>
		/// <param name="item">
		/// The item.
		/// </param>
		/// <param name="index">
		/// The group index to apply to the item.
		/// </param>
		public static void SetIndex(DependencyObject item, int? index)
		{
			ArgumentHelper.AssertNotNull(item, "item");
			item.SetValue(IndexProperty, index);
		}

		private static GroupedAndSortedItems GetOwner(DependencyObject item)
		{
			ArgumentHelper.AssertNotNull(item, "item");
			return item.GetValue(OwnerProperty) as GroupedAndSortedItems;
		}

		private static void SetOwner(DependencyObject item, GroupedAndSortedItems groupedAndSortedItems)
		{
			ArgumentHelper.AssertNotNull(item, "item");
			item.SetValue(OwnerProperty, groupedAndSortedItems);
		}

		private static void GroupIndex_Changed(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
		{
			GroupedAndSortedItems owner = GetOwner(dependencyObject);

			if (owner != null)
			{
				owner.OnCollectionChanged();
			}
		}

		private static void Index_Changed(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
		{
			GroupedAndSortedItems owner = GetOwner(dependencyObject);

			if (owner != null)
			{
				owner.OnCollectionChanged();
			}
		}

		//binding to attached property: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2158190&SiteID=1

		private sealed class GroupedAndSortedItems : IEnumerable, INotifyCollectionChanged, IComparer<object>, IValueConverter
		{
			private readonly ItemsControl _itemsControl;
			private readonly List<object> _items;

			public event NotifyCollectionChangedEventHandler CollectionChanged;

			public GroupedAndSortedItems(ItemsControl itemsControl)
			{
				Debug.Assert(itemsControl != null);
				_itemsControl = itemsControl;
				_items = new List<object>();
			}

			public IEnumerator GetEnumerator()
			{
				int? currentGroup = null;
				bool separatorRequired = false;

				foreach (object o in _items)
				{
					DependencyObject dependencyObject = o as DependencyObject;
					UIElement uiElement = o as UIElement;

					if (dependencyObject != null)
					{
						int? group = GetGroupIndex(dependencyObject);

						if (group != currentGroup)
						{
							currentGroup = group;

							if (separatorRequired)
							{
								separatorRequired = false;
								yield return new Separator();
							}
						}
					}

					if ((uiElement != null) && (uiElement.Visibility == Visibility.Visible))
					{
						separatorRequired = true;
					}

					yield return o;
				}
			}

			public void Add(object o)
			{
				_items.Add(o);
				_items.Sort(this);
				//AttachTo(o);
				OnCollectionChanged();
			}

			public void Remove(object o)
			{
				if (_items.Remove(o))
				{
					DetachFrom(o);
					OnCollectionChanged();
				}
			}

			private void AttachTo(object o)
			{
				UIElement uiElement = o as UIElement;

				if (uiElement != null)
				{
					Binding binding = new Binding("Visibility");
					binding.Converter = this;
					binding.Source = uiElement;
					binding.Mode = BindingMode.OneWay;
					BindingOperations.SetBinding(uiElement, UIElement.VisibilityProperty, binding);
				}
			}

			private void DetachFrom(object o)
			{
				UIElement uiElement = o as UIElement;

				if (uiElement != null)
				{
					BindingOperations.ClearBinding(uiElement, UIElement.VisibilityProperty);
				}
			}

			int IComparer<object>.Compare(object x, object y)
			{
				int? xGroupIndex = null;
				int? yGroupIndex = null;
				int? xIndex = null;
				int? yIndex = null;

				DependencyObject xDependencyObject = x as DependencyObject;
				DependencyObject yDependencyObject = y as DependencyObject;

				if (xDependencyObject != null)
				{
					xGroupIndex = GetGroupIndex(xDependencyObject);
					xIndex = GetIndex(xDependencyObject);
				}

				if (yDependencyObject != null)
				{
					yGroupIndex = GetGroupIndex(yDependencyObject);
					yIndex = GetIndex(yDependencyObject);
				}

				//there is a little bug in this logic: items with explicit group index int.MinValue will be grouped with items with
				//no explicit group index. Makes the code simpler, though

				int groupCompare = xGroupIndex.GetValueOrDefault(int.MinValue).CompareTo(yGroupIndex.GetValueOrDefault(int.MinValue));

				if (groupCompare == 0)
				{
					//same group, so compare indexes
					return xIndex.GetValueOrDefault(int.MinValue).CompareTo(yIndex.GetValueOrDefault(int.MinValue));
				}
				else
				{
					return groupCompare;
				}
			}

			object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
			{
				//HACK: when visibility on UIElement changes, refresh the collection
				OnCollectionChanged();
				return DependencyProperty.UnsetValue;
			}

			object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
			{
				throw new InvalidOperationException();
			}

			public void OnCollectionChanged()
			{
				EventHelper.Raise(CollectionChanged, this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
			}
		}
	}
}
